namespace ScrumSprintMonitor.Implementation.AvailableTimeCalculator
{
	#region #using Directives

	using System;
	using System.Diagnostics;

	using Infrastructure;

	#endregion

	public class SimpleTeamMemberAvailableTimeCalculator : ITeamMemberAvailableTimeCalculator
	{
		private static readonly TimeSpan FreeBusySlotSize = TimeSpan.FromMinutes(15);

		#region Implementation of ITeamMemberAvailableTimeCalculator

		public bool HasFreeBusyInformationFor(ITeamMemberInformation teamMemberInfo)
		{
			return false;
		}

		/// <summary>
		/// Get the available work hours for the given team member.
		/// </summary>
		/// <param name="teamMemberInfo">The information of the team member for which to get the available hours.</param>
		/// <param name="start">Start time of the range, in the target time zone.</param>
		/// <param name="end">End time of the range, in the target time zone.</param>
		/// <returns>The available work hours for the given team member.</returns>
		public double GetAvailableHours(ITeamMemberInformation teamMemberInfo, DateTime start, DateTime end, TimeCalculationOptions options)
		{
			// Times must be handled in the TimeZone of the team member. We will use DateTimeKind.Unspecified to convey that meaning.

			if (teamMemberInfo == null)
			{
				throw new ArgumentNullException("teamMemberInfo");
			}

			start = start.ToTeamMemberTimeZone(teamMemberInfo);
			end = end.ToTeamMemberTimeZone(teamMemberInfo);
			Debug.Assert(start.Kind == DateTimeKind.Unspecified);
			Debug.Assert(end.Kind == DateTimeKind.Unspecified);

			bool futureOnly = (options == TimeCalculationOptions.FutureOnly);
			double remainingSprintHours = 0.0;
			var currentDateTime = start;
			while (currentDateTime < end)
			{
				var todaysStartTime = GetWorkdayStart(teamMemberInfo, currentDateTime, futureOnly);
				var todaysEndTime = GetWorkdayEnd(teamMemberInfo, currentDateTime);

				if (todaysStartTime < start)
				{
					todaysStartTime = start;
				}
				if (todaysEndTime > end)
				{
					todaysEndTime = end;
				}

				remainingSprintHours += GetAvailableHoursInDay(teamMemberInfo, todaysStartTime, todaysEndTime, futureOnly);

				currentDateTime = GetWorkdayStart(teamMemberInfo, currentDateTime.AddDays(1.0), futureOnly);
			}

			return remainingSprintHours;
		}

		private static DateTime GetWorkdayStart(ITeamMemberInformation teamMemberInfo, DateTime dateTime, bool futureOnly)
		{
			Debug.Assert(dateTime.Kind == DateTimeKind.Unspecified);

			var startTime = teamMemberInfo.GetWorkdayStart(dateTime);
			var now = SystemTime.Now().ToTeamMemberTimeZone(teamMemberInfo);

			if (futureOnly && startTime < now)
			{
				startTime = now;
			}

			return startTime;
		}

		private static DateTime GetWorkdayEnd(ITeamMemberInformation teamMemberInfo, DateTime dateTime)
		{
			return teamMemberInfo.GetWorkdayEnd(dateTime).ToTeamMemberTimeZone(teamMemberInfo);
		}

		private static double GetAvailableHoursInDay(ITeamMemberInformation teamMemberInfo, DateTime time, DateTime todaysEndTime, bool futureOnly)
		{
			if (time < todaysEndTime && time.DayOfWeek != DayOfWeek.Saturday && time.DayOfWeek != DayOfWeek.Sunday)
			{
				var workDayStart = GetWorkdayStart(teamMemberInfo, time, futureOnly);
				var workDayEnd = GetWorkdayEnd(teamMemberInfo, time);

				if (workDayStart > time)
				{
					time = workDayStart;
				}

				var freeTime = new TimeSpan();
				var selectedFreeBusySlotSize = FreeBusySlotSize;

				while (time < workDayEnd)
				{
					var thisSlotSize = new TimeSpan(selectedFreeBusySlotSize.Ticks - (time.TimeOfDay.Ticks % selectedFreeBusySlotSize.Ticks));

					if (!teamMemberInfo.IsBusy(time, thisSlotSize))
					{
						freeTime = freeTime.Add(thisSlotSize);
					}

					time = time.Add(thisSlotSize);
				}

				return freeTime.TotalHours;
			}

			return 0.0;
		}

		#endregion
	}
}